﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public
 * License, as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Collections.Generic;
using DocumentFormat.OpenXml.Packaging;

namespace ExtremeML.Core
{
	/// <summary>
	/// Serves as the base class for classes that wrap packages, parts or elements.
	/// </summary>
	public abstract class WrapperBase<TWrapped> : IContentWrapper
	{
		#region Housekeeping

		/// <summary>
		/// Initializes a new instance of the <see cref="WrapperBase{TWrapped}"/> class.
		/// </summary>
		/// <param name="wrapped">An <see cref="OpenXmlPackage"/> from which to populate this instance.</param>
		protected WrapperBase(
			TWrapped wrapped)
		{
			if (wrapped == null) throw new ArgumentNullException("wrapped");
			if (!typeof(OpenXmlPackage).IsAssignableFrom(wrapped.GetType())) throw new InvalidCastException("This constructor overload is restricted to package wrapper classes.");

			Wrapped = wrapped;
			OnConstructing();
			Decompose();
		}
		
		/// <summary>
		/// Initializes a new instance of the <see cref="WrapperBase{TWrapped}"/> class.
		/// </summary>
		/// <param name="wrapped">An OpenXml content object from which to populate this instance.</param>
		/// <param name="parent">An <see cref="IContentWrapper"/> that represents the parent wrapper instance.</param>
		/// <param name="package">An <see cref="IContentWrapper"/> that represents the package wrapper instance.</param>
		protected WrapperBase(
			TWrapped wrapped,
			IContentWrapper parent,
			IContentWrapper package)
		{
			if (parent == null) throw new ArgumentNullException("parent");
			if (package == null) throw new ArgumentNullException("package");

			Wrapped = wrapped;
			Parent = parent;
			Package = package;

			OnConstructing();

			if (Wrapped == null)
			{
				Initialize();
			}
			else
			{
				Decompose();
			}
		}

		#endregion
		#region Properties

		/// <summary>
		/// Gets the root <see cref="IContentWrapper"/> instance.
		/// </summary>
		public IContentWrapper Package { get; private set; }

		/// <summary>
		/// Gets the parent <see cref="IContentWrapper"/> instance.
		/// </summary>
		public IContentWrapper Parent { get; private set; }

		/// <summary>
		/// Gets the document content object from which this instance was populated.
		/// </summary>
		protected internal TWrapped Wrapped { get; protected set; }
		
		/// <summary>
		/// Gets a value that indicates whether the content of this this instance has been modified.
		/// </summary>
		protected internal bool IsModified { get; protected set; }

		#endregion
		#region Events

		/// <summary>
		/// Occurs during the constructor if this instance was created with a <c>null</c> wrapped content object.
		/// </summary>
		protected event VoidEventHandler Initializing;

		/// <summary>
		/// Occurs during the constructor if this instance was created with a non-<c>null</c> wrapped content object.
		/// </summary>
		protected event VoidEventHandler Decomposing;

		/// <summary>
		/// Occurs when the wrapper hierarchy is fully populated and ready to support inter-wrapper references.
		/// </summary>
		protected event VoidEventHandler Activating;

		/// <summary>
		/// Occurs prior to persistence of the wrapper hierarchy while it is still guaranteed to be fully intact.
		/// </summary>
		protected event VoidEventHandler Deactivating;

		#endregion
		#region Delegates

		/// <summary>
		/// Represents the method signature that will handle events with no arguments.
		/// </summary>
		protected delegate void VoidEventHandler();

		/// <summary>
		/// Represents the method signature that will handle events involving wrapped content.
		/// </summary>
		protected delegate void WrappedContentEventHandler(TWrapped wrapped);

		#endregion
		#region Life-Cycle Methods

		/// <summary>
		/// When overridden in a derived generated partial class,
		/// populates the derived instance using default values.
		/// </summary>
		/// <remarks>
		/// This method is called by the class constructor when the wrapped object is <c>null</c>.
		/// </remarks>
		internal void Initialize()
		{
			IsModified = false;

			// Execute any initialization code defined in generated partial classes.
			OnInitializing();

			// Execute any initialization code defined in custom partial classes.
			OnCustomInitializing();

			// Execute any initialization code defined in pattern event listeners.
			if (Initializing != null) Initializing();
		}

		/// <summary>
		/// When overridden in a derived generated partial class, extracts data from the wrapped content
		/// object and uses it to populate the scalar and wrapper members of the derived class instance.
		/// </summary>
		/// <remarks>
		/// This method is called by the class constructor when the wrapped object is not <c>null</c>.
		/// </remarks>
		protected void Decompose()
		{
			IsModified = false;

			// Execute any decompose code defined in generated partial classes.
			OnDecomposing();

			// Execute any decompose code defined in custom partial classes.
			OnCustomDecomposing();

			// Execute any decompose code defined in pattern event listeners.
			if (Decomposing != null) Initializing();
		}

		/// <summary>
		/// Establishes relationships with other package content objects.
		/// </summary>
		internal void Activate()
		{
			// Execute any activation code defined in generated partial classes.
			OnActivating();
	
			// Execute any activation code defined in custom partial classes.
			OnCustomActivating();

			// Execute any activation code defined in pattern event listeners.
			if (Activating != null) Activating();

			// Propagate activation to nested wrapper instances.
			var members = new List<IWrapper>(GetWrapperMembers());
			foreach (var member in members)
			{
				member.Activate();
			}
		}

		/// <summary>
		/// Safely terminates relationships with other package content objects.
		/// </summary>
		internal void Deactivate()
		{
			// Execute any deactivation code defined in generated partial classes.
			OnDeactivating();

			// Execute any deactivation code defined in custom partial classes.
			OnCustomDeactivating();

			// Execute any deactivation code defined in pattern event listeners.
			if (Deactivating != null) Deactivating();

			// Propagate deactivation to nested wrapper instances.
			var members = new List<IWrapper>(GetWrapperMembers());
			foreach (var member in members)
			{
				member.Deactivate();
			}
		}
		
		#endregion
		#region Generated Life-Cycle Methods

		/// <summary>
		/// When overridden in a derived generated partial class, declares code
		/// that be must executed prior to initialization and decompose code.
		/// </summary>
		/// <remarks>
		/// This method is called by the constructor of this class.
		/// </remarks>
		protected virtual void OnConstructing()
		{
		}

		/// <summary>
		/// When overridden in a derived generated partial class, declares custom initialization functionality.
		/// </summary>
		/// <remarks>
		/// This method is called by the <see cref="Initialize"/> method of the generated partial class.
		/// </remarks>
		protected virtual void OnInitializing()
		{
		}

		/// <summary>
		/// When overridden in a derived generated partial class, declares custom wrapper population functionality.
		/// </summary>
		/// <remarks>
		/// This method is called by the <see cref="Decompose"/> method of the generated partial class.
		/// </remarks>
		protected virtual void OnDecomposing()
		{
		}

		/// <summary>
		/// When overridden in a derived generated partial class, declares custom wrapper activation functionality.
		/// </summary>
		/// <remarks>
		/// This method is called by the <see cref="Activate"/> method of this class.
		/// </remarks>
		protected virtual void OnActivating()
		{
		}

		/// <summary>
		/// When overridden in a derived generated partial class, declares custom wrapper deactivation functionality.
		/// </summary>
		/// <remarks>
		/// This method is called by the <see cref="Deactivate"/> method of this class.
		/// </remarks>
		protected virtual void OnDeactivating()
		{
		}

		#endregion
		#region Custom Life-Cycle Methods

		/// <summary>
		/// When overridden in a derived custom partial class, declares custom initialization functionality.
		/// </summary>
		/// <remarks>
		/// This method is called by the <see cref="Initialize"/> method of the generated partial class.
		/// </remarks>
		protected virtual void OnCustomInitializing()
		{
		}

		/// <summary>
		/// When overridden in a derived custom partial class, declares custom wrapper population functionality.
		/// </summary>
		/// <remarks>
		/// This method is called by the <see cref="Decompose"/> method of the generated partial class.
		/// </remarks>
		protected virtual void OnCustomDecomposing()
		{
		}

		/// <summary>
		/// When overridden in a derived custom partial class, declares custom wrapper activation functionality.
		/// </summary>
		/// <remarks>
		/// This method is called by the <see cref="Activate"/> method of this class.
		/// </remarks>
		protected virtual void OnCustomActivating()
		{
		}

		/// <summary>
		/// When overridden in a derived custom partial class, declares custom wrapper deactivation functionality.
		/// </summary>
		/// <remarks>
		/// This method is called by the <see cref="Deactivate"/> method of this class.
		/// </remarks>
		protected virtual void OnCustomDeactivating()
		{
		}

		#endregion
		#region Content Management Methods

		/// <summary>
		/// Compares the passed values and conditionally sets the <see cref="IsModified"/> property.
		/// </summary>
		protected void SetModified(
			object modified,
			object original)
		{
			if (modified == null)
			{
				IsModified = original != null;
			}
			else if (!modified.Equals(original))
			{
				IsModified = true;
			}
		}

		/// <summary>
		/// Returns a value that indicates whether this instance wraps content that should be saved to the document.
		/// </summary>
		/// <returns><c>true</c> when the instance was constructed with a non-null member value or when
		/// the value of a scalar or wrapper property has been modified; otherwise, <c>false</c>.</returns>
		internal virtual bool IsPopulated()
		{
			if (GetWrappedContentObject() != null || IsModified)
			{
				// A wrapper is always populated if it contains a non-null content
				// object or if any scalar or simple property value is modified
				return true;
			}
			
			// It is also populated if any of its nested wrapper instances are populated
			foreach (var member in GetWrapperMembers())
			{
				if (member.IsPopulated())
				{
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// When overridden in a derived generated partial class, returns the set of <see cref="IContentWrapper"/>
		/// instances (properties or collection members) that wrap nested content objects.
		/// </summary>
		protected virtual IEnumerable<IWrapper> GetWrapperMembers()
		{
			yield break;
		}

		/// <summary>
		/// When overridden in a derived custom partial class, returns the set of
		/// <see cref="IContentWrapper"/> instances defined as custom properties.
		/// </summary>
		protected virtual IEnumerable<IWrapper> GetCustomWrapperMembers()
		{
			yield break;
		}

		#endregion
		#region IContentWrapper Implementation

		IContentWrapper IContentWrapper.Package
		{
			get { return Package; }
		}

		IContentWrapper IContentWrapper.Parent
		{
			get { return Parent; }
		}

		void IWrapper.Activate()
		{
			Activate();
		}

		void IWrapper.Deactivate()
		{
			Deactivate();
		}

		bool IWrapper.IsPopulated()
		{
			return IsPopulated();
		}

		/// <summary>
		/// Returns the content <see cref="object"/> from which this instance was populated.
		/// </summary>
		public object GetWrappedContentObject()
		{
			return Wrapped;
		}

		#endregion
	}
}
